// 17.1 标准库特殊设施——tuple类型
/**
 * tuple是类似pair（参见11.2.3节，第379页）的模板。每个pair的成员类型都不相同，但每个pair都恰好有两个成员。不同tuple类型的成员类型也不相同，但一个tuple可以有任意数量的成员。
 *   每个确定的tuple类型的成员数目是固定的，但一个tuple类型的成员数目可以与另一个tuple类型不同。
 * 当我们希望将一些数据组合成单一对象，但又不想麻烦地定义一个新数据结构来表示这些数据时，tuple是非常有用的。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>

int main()
{
    // 17.1.1 定义和初始化tuple
    /*
当我们定义一个tuple时，需要指出每个成员的类型：
tuple<size_t, size_t, size_t> threeD;         // tuple的默认构造，对每个成员进行值初始化；三个成员都设置为0
tuple<string, vector<double>, int, list<int>> // 为每个成员提供一个初始值
  someVal("hi", {1.2, 2.3, 4.5}, 7, {1, 2, 3, 4, 5}");
也可以像本例中初始化someVal一样，为每个成员提供一个初始值。tuple的这个构造函数是explicit的（参见7.5.4节，第265页），因此我们必须使用直接初始化语法：
tuple<size_t, size_t, size_t> threeD = {1,2,3}; // 错误
tuple<size_t, size_t, size_t> threeD(1,2,3);    // 正确
标准库定义了make_tuple函数，我们还可以用它来生成tuple对象：[插图]
  // 表示书店交易的tuple，包含：ISBN、数量和每册书的价格
  auto item = make_tuple("0-999-78345-X",3, 20);
类似make_pair，make_tuple函数使用初始值的类型来推断tuple的类型。
    */

    // 访问tuple的成员
    /*
要访问一个tuple的成员，就要使用一个名为get的标准库函数模板。为了使用get，我们必须指定一个显式模板实参（参见16.2.2节，第603页），它指出我们想要访问第几个成员。
  我们传递给get一个tuple对象，它返回指定成员的引用：
auto book = get<0>(item);      // 返回item的第一个成员
auto cnt = get<1>(item);       // 返回item的第二个成员
auto price = get<2>(item)/cnt; // 返回item的第三个成员
get<2>(item) *= 0.8;           // 打折20%
如果不知道一个tuple准确的类型细节信息，可以用两个辅助类模板来查询tuple成员的数量和类型：
typedef decltype(item) trans; // trans时item的类型
size_t sz = tuple_size<trans>::value; // 返回3
tuple_element<1, trans>::type cnt = get<1>(item); // cnt是个int
为了使用tuple_size或tuple_element，我们需要知道一个tuple对象的类型。与往常一样，确定一个对象的类型的最简单方法就是使用decltype（参见2.5.3节，第62页）。
    */

    // 关系和相等运算符
    /*
tuple的关系和相等运算符的行为类似容器的对应操作（参见9.2.7节，第304页）。这些运算符逐对比较左侧tuple和右侧tuple的成员。只有两个tuple具有相同数量的成员时，我们才可以比较它们。
  而且，为了使用tuple的相等或不等运算符，对每对成员使用==运算符必须都是合法的；为了使用关系运算符，对每对成员使用<必须都是合法的。
由于tuple定义了<和==运算符，我们可以将tuple序列传递给算法，并且可以在无序容器中将tuple作为关键字类型。
    */

    // 17.1.2 使用tuple返回多个值
    // tuple的一个常见用途是从一个函数返回多个值。

    // 返回tuple的函数
    /*
// maches有三个成员：一家书店的索引和两个指向书店vector中元素的迭代器
typedef tuple<vector<Sales_data>::size_type,
              vector<Sales_data>::const_iterator,
              vector<Sales_data>::const_iterator> matches;  
// files保存每家书店的销售记录
// findBook返回一个vector，每家销售了给定书籍的书店在其中都有一项
vector<matches>
findBook(const vector<vector<Sales_data>> &files,
         const string &book)
{
  vector<matches> ret; // 初始化为空vector
  // 对每家书店，查找与给定书籍匹配的记录范围
  for(auto it = files.cbegin(); it!= files.cend(); ++it) {
      // 查找具有相同ISBN的Sales_data范围
      auto found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);
      if(found.first != found.second) // 此书店销售了给定书籍
        // 记住此书店的索引及匹配的范围
        ret.push_back(make_tulpe(it -files.cbegin(), found.first, found.second));
  }
  return ret; // 如果未找到匹配记录的话，ret为空
}
for循环遍历files中的元素，每个元素都是一个vector。在for循环内，我们调用了一个名为equal_range的标准库算法，它的功能与关联容器的同名成员类似（参见11.3.5节，第390页）。
  equal_range的前两个实参是表示输入序列的迭代器（参见10.1节，第336页），第三个参数是一个值。默认情况下，equal_range使用<运算符来比较元素。
  由于Sales_data没有<运算符，因此我们传递给它一个指向compareIsbn函数的指针（参见11.2.2节，第379页）。
equal_range算法返回一个迭代器pair，表示元素的范围。如果未找到book，则两个迭代器相等，表示空范围。否则，返回的pair的first成员将表示第一条匹配的记录，second则表示匹配的尾后位置。
    */

    // 使用函数返回的tuple
    /*
一旦我们创建了vector保存包含匹配的销售记录的书店，就需要处理这些记录了。在此程序中，对每家包含匹配销售记录的书店，我们将打印其汇总销售信息：
void reportResults(istream &in, ostream &os, const vector<vector<Sales_data>> &files)
{
  string s; // 要查找的书
  while(in >> s) {
    auto trans = findBook(files, s); // 销售了这本书的书店
    if(trans.empty()) {
      cout << s << "not found in any sotres" << endl;
      continue; // 获得下一本要查找的书
    }
    for(const auto &store : trans) // 对每家销售了给定书籍的书店
      os << "store " << get<0>(store) << " sales: "
         << accumulate(get<1>(store), get<2>(store), Sales_data(s)) << endl;
  }
}
由于Sales_data定义了加法运算符（参见14.3节，第497页），因此我们可以用标准库的accumulate算法（参见10.2.1节，第338页）来累加销售记录。
  我们用Sales_data的接受一个string参数的构造函数（参见7.1.4节，第236页）来初始化一个Sales_data对象，将此对象传递给accumulate作为求和的起点。
  此构造函数用给定的string初始化bookNo，并将units_sold和revenue成员置为0。
    */

    return 0;
}